//The MIT License (MIT)
//
//Copyright (c) 2020 lihp1603
//
//Permission is hereby granted, free of charge, to any person obtaining a copy
//of this software and associated documentation files (the "Software"), to deal
//in the Software without restriction, including without limitation the rights
//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//copies of the Software, and to permit persons to whom the Software is
//furnished to do so, subject to the following conditions:
//
//The above copyright notice and this permission notice shall be included in
//all copies or substantial portions of the Software.
//
//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
//THE SOFTWARE.

package proc

import (
	"io/ioutil"
	"strconv"
	"strings"
	"time"
)

func ReadProcSystemStat() (*ProcSystemStat, error) {
	return readProcStat("/proc/stat")
}

func readProcStat(path string) (*ProcSystemStat, error) {
	b, err := ioutil.ReadFile(path)
	if err != nil {
		return nil, err
	}
	content := string(b)
	lines := strings.Split(content, "\n")

	sysstat := ProcSystemStat{}

	for i, line := range lines {
		fields := strings.Fields(line)
		if len(fields) == 0 {
			continue
		}
		if fields[0][:3] == "cpu" {
			if cpuStat := createCPUStat(fields); cpuStat != nil {
				if i == 0 {
					sysstat.CPUStatAll = cpuStat
				} else {
					sysstat.CPUCoreStats = append(sysstat.CPUCoreStats, cpuStat)
				}
			}
		} else if fields[0] == "intr" {
			sysstat.Interrupts, _ = strconv.ParseUint(fields[1], 10, 64)
		} else if fields[0] == "ctxt" {
			sysstat.ContextSwitches, _ = strconv.ParseUint(fields[1], 10, 64)
		} else if fields[0] == "btime" {
			seconds, _ := strconv.ParseInt(fields[1], 10, 64)
			sysstat.BootTime = time.Unix(seconds, 0)
		} else if fields[0] == "processes" {
			sysstat.Processes, _ = strconv.ParseUint(fields[1], 10, 64)
		} else if fields[0] == "procs_running" {
			sysstat.ProcsRunning, _ = strconv.ParseUint(fields[1], 10, 64)
		} else if fields[0] == "procs_blocked" {
			sysstat.ProcsBlocked, _ = strconv.ParseUint(fields[1], 10, 64)
		}
	}
	//更新一下当前的时间
	sysstat.UpdateSampledTime(nil)
	return &sysstat, nil
}

//记录的proc/stat的信息
type ProcSystemStat struct {
	SampledTime
	CPUStatAll      *CPUStat   `json:"cpu_all"`
	CPUCoreStats    []*CPUStat `json:"cpus"`
	Interrupts      uint64     `json:"intr"`
	ContextSwitches uint64     `json:"ctxt"`
	BootTime        time.Time  `json:"btime"`
	Processes       uint64     `json:"processes"`
	ProcsRunning    uint64     `json:"procs_running"`
	ProcsBlocked    uint64     `json:"procs_blocked"`
}

func (pss *ProcSystemStat) GetCpuStatAll() *CPUStat {
	return pss.CPUStatAll
}

//cpu
type CPUStat struct {
	Id        string `json:"id"`
	User      uint64 `json:"user"`
	Nice      uint64 `json:"nice"`
	System    uint64 `json:"system"`
	Idle      uint64 `json:"idle"`
	IOWait    uint64 `json:"iowait"`
	IRQ       uint64 `json:"irq"`
	SoftIRQ   uint64 `json:"softirq"`
	Steal     uint64 `json:"steal"`
	Guest     uint64 `json:"guest"`
	GuestNice uint64 `json:"guest_nice"`
}

func createCPUStat(fields []string) *CPUStat {
	s := CPUStat{}
	s.Id = fields[0]

	for i := 1; i < len(fields); i++ {
		v, _ := strconv.ParseUint(fields[i], 10, 64)
		switch i {
		case 1:
			s.User = v
		case 2:
			s.Nice = v
		case 3:
			s.System = v
		case 4:
			s.Idle = v
		case 5:
			s.IOWait = v
		case 6:
			s.IRQ = v
		case 7:
			s.SoftIRQ = v
		case 8:
			s.Steal = v
		case 9:
			s.Guest = v
		case 10:
			s.GuestNice = v
		}
	}
	return &s
}

func (cs *CPUStat) GetCpuIdle() uint64 {
	return cs.Idle + cs.IOWait
}

func (cs *CPUStat) GetCpuTotal() uint64 {
	// Guest time is already accounted in usertime
	usertime := cs.User - cs.Guest     //As you see here, it subtracts guest from user time
	nicetime := cs.Nice - cs.GuestNice // and guest_nice from nice time
	// Fields existing on kernels >= 2.6
	// (and RHEL's patched kernel 2.4...)
	idlealltime := cs.Idle + cs.IOWait // ioWait is added in the idleTime
	systemalltime := cs.System + cs.IRQ + cs.SoftIRQ
	virtalltime := cs.Guest + cs.GuestNice
	steal := cs.Steal
	total := usertime + nicetime + systemalltime + idlealltime + steal + virtalltime
	return total
}

func (cs *CPUStat) GetCpuTotalTicks() uint64 {
	return cs.User + cs.Idle + cs.System + cs.Nice + cs.IOWait + cs.SoftIRQ + cs.IRQ + cs.Steal + cs.Guest
}
